/*
 * Copyright Notice ====================================================
 * This file contains proprietary information of Hewlett-Packard Co.
 * Copying or reproduction without prior written approval is prohibited.
 * Copyright (c) 2013 All rights reserved. =============================
 */

package com.hp.ecs.xqa.util;

import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.stereotype.Component;

@Component
public class XmlUtil {
	private static final String ELEMENT_BEAN = "bean";

	private static final String ELEMENT_LIST = "list";

	private static final String ATTRIBUTE_CLASS = "class";

	private static final String PACKAGE_TC_DATA = "com.hp.ecs.tc.data.";

	public static final String ATTRIBUTE_METHOD = "method";

	public static final String ROOT = "task";

	public static final String ATTRIBUTE_MODULE = "module";

	public static final String DATE_FORMAT = DateUtil.DATETIME_PATTERN;

	public String toXml(String module, String method, Object... objects) {
		Document document = DocumentHelper.createDocument();
		Element root = document.addElement(ROOT);
		root.addAttribute(ATTRIBUTE_MODULE, module);
		root.addAttribute(ATTRIBUTE_METHOD, method);
		for (Object object : objects) {
			addElement(root, object);
		}
		return document.asXML();
	}

	private void addElement(Element root, Object object) {
		if (object instanceof Iterable) {
			Element list = root.addElement(ELEMENT_LIST);
			for (Object o : (Iterable)object) {
				addElement(list, o);
			}
		} else {
			Element e = root.addElement(ELEMENT_BEAN);
			final Class<? extends Object> clazz = object.getClass();
			if (clazz.getName().startsWith(PACKAGE_TC_DATA)) {
				e.addAttribute(ATTRIBUTE_CLASS, clazz.getName().substring(PACKAGE_TC_DATA.length()));
			} else {
				e.addAttribute(ATTRIBUTE_CLASS, clazz.getName());
			}
			for (Method m : clazz.getMethods()) {
				if (ReflectionUtil.getSetter(m) == null) {
					continue;
				}
				Object value = getValue(m, object);
				if (value == null) {
					continue;
				}
				Element fe = e.addElement(getFieldName(m.getName()));
				if (value instanceof String) {
					fe.setText((String)value);
				} else {
					addElement(fe, value);
				}
			}
		}
	}

	private Object getValue(Method m, Object object) {
		Object r;
		try {
			r = m.invoke(object);
		} catch (IllegalAccessException e) {
			throw new IllegalStateException(e);
		} catch (InvocationTargetException e) {
			throw new IllegalStateException(e);
		}

		if (r instanceof String) {
			return r;
		} else if (r instanceof Number || r instanceof Boolean || r instanceof Character) {
			return r.toString();
		} else if (r instanceof Date) {
			return DateUtil.format((Date)r, DATE_FORMAT);
		} else {
			return r;
		}
	}

	private String getFieldName(final String name) {
		return StringUtil.unCapitalize(name.substring(name.startsWith("get") ? 3 : 2));
	}

	public String getMethod(String xml) {
		SAXReader reader = new SAXReader();
		Document document;
		try {
			document = reader.read(new StringReader(xml));
		} catch (DocumentException e) {
			throw new IllegalArgumentException("xml:" + xml, e);
		}
		return document.getRootElement().attributeValue(ATTRIBUTE_METHOD);
	}

	public Object[] parse(String xml) {
		List result = new ArrayList();
		try {
			SAXReader reader = new SAXReader();
			Document document = reader.read(new StringReader(xml));// .read(new File(fileName));
			Element root = document.getRootElement();

			for (Element e : (List<Element>)root.elements()) {
				result.add(parse(e));
			}
		} catch (IllegalAccessException e) {
			throw new IllegalArgumentException(xml, e);
		} catch (InvocationTargetException e) {
			throw new IllegalArgumentException(xml, e);
		} catch (DocumentException e) {
			throw new IllegalArgumentException(xml, e);
		}
		return result.toArray();
	}

	private Object parse(Element e) throws IllegalAccessException, InvocationTargetException {
		if (e.getName().equals(ELEMENT_LIST)) {
			List list = new ArrayList();
			for (Element ee : (List<Element>)e.elements()) {
				list.add(parse(ee));
			}
			return list;
		} else if (e.getName().equals(ELEMENT_BEAN)) {
			Class clazz = getClass(e.attributeValue(ATTRIBUTE_CLASS));
			Object o;
			try {
				o = clazz.newInstance();
			} catch (Exception e1) {
				throw new IllegalStateException(e1);
			}
			for (Element fe : (List<Element>)e.elements()) {
				setFieldValue(fe, o);
			}
			return o;
		} else {
			throw new IllegalArgumentException("Ukown element:" + e.getName());
		}
	}

	private Class getClass(final String name) {
		Class clazz;
		try {
			if (name.indexOf('.') > 0) {
				return Class.forName(name);
			} else {
				clazz = Class.forName(PACKAGE_TC_DATA + name);
			}
		} catch (ClassNotFoundException  e) {
			throw new IllegalArgumentException("Unkown class:" + name, e);
		}
		return clazz;
	}

	private void setFieldValue(Element fe, Object o) throws IllegalAccessException, InvocationTargetException {
		Method setter = ReflectionUtil.getSetter(fe.getName(), o.getClass());
		Class paraType = setter.getParameterTypes()[0];
		Object para;
		if (fe.elements().size() > 0) {
			para = parse((Element)fe.elements().get(0));
		} else {
			para = StringUtil.convert(fe.getText(), paraType);
		}
		setter.invoke(o, para);
	}
}
